package cn.lg.soar.core.aspect;

import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.common.util.lambda.IRunner;
import cn.lg.soar.core.annotations.SoarCache;
import cn.lg.soar.core.annotations.SoarCacheEvict;
import cn.lg.soar.core.annotations.SoarCaching;
import cn.lg.soar.core.config.cache.L2CacheManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author luguoxiang
 * @date 2021/8/24
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Aspect
@Order(0)
public class SoarCacheAspect {

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private L2CacheManager cacheManager;
    @Autowired
    @Qualifier("local")
    private L2CacheManager localCacheManager;
    @Autowired
    @Qualifier("remote")
    private L2CacheManager remoteCacheManager;
    @Value("${lg.database.enabledTenant:false}")
    private boolean enabledTenant;

    private static final Logger LOGGER = LoggerFactory.getLogger(SoarCacheAspect.class);

    @Around("@annotation(soarCache)")
    public Object around(ProceedingJoinPoint point, SoarCache soarCache) throws Throwable{
        Object value;
        Object key;
        Cache cache;
        // 1.解析缓存信息
        try {
            switch (soarCache.type()) {
                case local:
                    cache = localCacheManager.getCache(
                            soarCache.value(),
                            soarCache.localExpire(),
                            soarCache.remoteExpire()
                    );
                    break;
                case remote:
                    cache = remoteCacheManager.getCache(
                            soarCache.value(),
                            soarCache.localExpire(),
                            soarCache.remoteExpire()
                    );
                    break;
                default:
                    cache = cacheManager.getCache(
                            soarCache.value(),
                            soarCache.localExpire(),
                            soarCache.remoteExpire()
                    );
                    break;
            }
            AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext, null);
            if (soarCache.enabledTenant() || enabledTenant) {
                // 区分租户
                key = UserContext.getUser().getCurrentTenantId() +":"+ SpelExpressionUtils.getValue(soarCache.key(), context);
            } else {
                key = SpelExpressionUtils.getValue(soarCache.key(), context);
            }
        } catch (RuntimeException e) {
            LOGGER.error("解析缓存信息异常", e);
            return point.proceed();
        }

        // 2.查缓存数据
        try {
            value = cache.get(key, Object.class);
            if (value != null) {
                return value;
            }
        } catch (RuntimeException e) {
            LOGGER.error("查缓存数据异常", e);
        }

        // 3.调用方法
        value = point.proceed();
        if (value == null) {
            return null;
        }

        // 4.保存缓存数据
        try {
            Object finalValue = value;
            // 是否同步
            if (soarCache.sync()) {
                // 同步处理
                cache.put(key, finalValue);
            } else {
                // 异步处理
                CompletableFuture.runAsync(() -> cache.put(key, finalValue));
            }
        } catch (RuntimeException e) {
            LOGGER.error("保存缓存数据异常", e);
        }

        return value;
    }

    @Around("@annotation(cacheEvict)")
    public Object around(ProceedingJoinPoint point, SoarCacheEvict cacheEvict) throws Throwable{
        Object key = null;
        // 1.解析缓存信息
        try {
            // 解析key
            if (!cacheEvict.allEntries()) {
                AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext, null);
                if (cacheEvict.enabledTenant() || enabledTenant) {
                    // 区分租户
                    key = UserContext.getUser().getCurrentTenantId() +":"+ SpelExpressionUtils.getValue(cacheEvict.key(), context);
                } else {
                    key = SpelExpressionUtils.getValue(cacheEvict.key(), context);
                }
            }
        } catch (RuntimeException e) {
            LOGGER.error("解析缓存信息异常", e);
            return point.proceed();
        }

        Object value;
        switch (cacheEvict.strategy()) {
            case BEFORE:
                evict(cacheEvict, key);
                value = point.proceed();
                break;
            case AFTER:
                // 先执行方法，后删除缓存
                value = point.proceed();
                evict(cacheEvict, key);
                break;
            default:
                evict(cacheEvict, key);
                value = point.proceed();
                evict(cacheEvict, key);
                break;
        }
        return value;

    }

    @Around("@annotation(caching)")
    public Object around(ProceedingJoinPoint point, SoarCaching caching) throws Throwable{
        SoarCacheEvict[] evict = caching.evict();
        Map<SoarCacheEvict.Strategy, List<SoarCacheEvict>> group = Arrays.stream(evict).collect(Collectors.groupingBy(SoarCacheEvict::strategy));

        List<IRunner> before = new ArrayList<>();
        List<IRunner> after = new ArrayList<>();
        for (SoarCacheEvict cacheEvict : evict) {
            Object key;
            IRunner runner;
            // 1.解析缓存信息
            try {
                // 解析key
                if (cacheEvict.allEntries()) {
                    runner = () -> {
                        evict(cacheEvict, null);
                    };
                } else {
                    AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext, null);
                    if (cacheEvict.enabledTenant() || enabledTenant) {
                        // 区分租户
                        key = UserContext.getUser().getCurrentTenantId() +":"+ SpelExpressionUtils.getValue(cacheEvict.key(), context);
                    } else {
                        key = SpelExpressionUtils.getValue(cacheEvict.key(), context);
                    }
                    runner = () -> {
                        evict(cacheEvict, key);
                    };
                }
            } catch (RuntimeException e) {
                LOGGER.error("解析缓存信息异常", e);
                continue;
            }

            SoarCacheEvict.Strategy strategy = cacheEvict.strategy();
            if (strategy == SoarCacheEvict.Strategy.DOUBLE || strategy == SoarCacheEvict.Strategy.BEFORE) {
                before.add(runner);
            }
            if (strategy == SoarCacheEvict.Strategy.DOUBLE || strategy == SoarCacheEvict.Strategy.AFTER) {
                after.add(runner);
            }
        }
        // 先删除缓存，后执行方法，再删除缓存
        for (IRunner runner : before) {
            runner.execute();
        }
        Object value = point.proceed();
        for (IRunner runner : after) {
            runner.execute();
        }
        return value;
    }

    private void evict(SoarCacheEvict cacheEvict, Object key) {
        Cache cache;
        // 删除本地缓存
        cache = localCacheManager.getCacheNullable(cacheEvict.value());
        if (cache != null) {
            if (cacheEvict.allEntries()) {
                cache.invalidate();
            } else {
                cache.evict(key);
            }
        }

        // 删除远程换行
        cache = remoteCacheManager.getCacheNullable(cacheEvict.value());
        if (cache != null) {
            if (cacheEvict.allEntries()) {
                cache.invalidate();
            } else {
                cache.evict(key);
            }
        }

        // 删除二级缓存
        cache = cacheManager.getCacheNullable(cacheEvict.value());
        if (cache != null) {
            if (cacheEvict.allEntries()) {
                cache.invalidate();
            } else {
                cache.evict(key);
            }
        }
    }
}
